package cn.chengpengper.rpc.common.scanner;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * @author chengpeng.hu
 * @description 类扫描器
 */
@Slf4j
public class ClassScanner {

    /**
     * 文件
     * 扫描当前工程中指定包下的所有类信息
     */
    private static final String PROTOCOL_FILE = "file";

    /**
     * jar 包
     * 扫描Jar文件中指定包下的所有类信息
     */
    private static final String PROTOCOL_JAR = "jar";

    /**
     * class 文件后缀
     * 扫描的过程中指定需要处理的文件的后缀信息
     */
    private static final String CLASS_FILE_SUFFIX = ".class";

    public static List<String> getClassNameList(String packageName) {
        ArrayList<String> classNameList = new ArrayList<>();
        final boolean recursive = true;
        String packageDirName = packageName.replace('.', '/');
        try {
            Enumeration<URL> dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
            while (dirs.hasMoreElements()) {
                URL url = dirs.nextElement();
                //得到协议的名称
                String protocol = url.getProtocol();
                //如果是以文件的形式保存在服务器上
                if (PROTOCOL_FILE.equals(protocol)) {
                    //获取包的物理路径
                    String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                    findAndAddClassesInPackageByFile(packageName, filePath, recursive, classNameList);
                } else if (PROTOCOL_JAR.equals(protocol)) {
                    packageName = findAndAddClassesInPackageByJar(packageName, classNameList, recursive, packageDirName, url);
                }
            }
        } catch (IOException e) {
            log.error("getClassNameList|Has a exception,", e);
        }
        return classNameList;
    }

    /**
     * 扫描当前工程中指定包下的所有类信息
     *
     * @param packageName   扫描的包名
     * @param packagePath   包在磁盘上的完整路径
     * @param recursive     是否递归扫描
     * @param classNameList 类名称集合
     */
    private static void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive, List<String> classNameList) {
        File dir = new File(packagePath);
        // 如果不存在或者也不是目录就直接返回
        if (!dir.exists() && !dir.isDirectory()) {
            return;
        }
        File[] dirFiles = dir.listFiles(file -> {
            // （如果可以递归扫描且当前文件是目录) 或则是以.class结尾的文件(编译好的java类文件)
            return (recursive && file.isDirectory()) || file.getName().endsWith(CLASS_FILE_SUFFIX);
        });

        if (Objects.isNull(dirFiles)) {
            return;
        }

        for (File file : dirFiles) {
            // 如果是目录则递归扫描
            if (file.isDirectory()) {
                findAndAddClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive, classNameList);
            } else {
                // 如果是java类文件，去掉后面的.class只留下类名
                String className = file.getName().substring(0, file.getName().length() - 6);
                classNameList.add(packageName + "." + className);
            }
        }
    }

    /**
     * 扫描Jar文件中指定包下的所有类信息
     *
     * @param packageName    扫描的包名
     * @param classNameList  完成类名存放的List集合
     * @param recursive      是否递归调用
     * @param packageDirName 当前包名的前面部分的名称
     * @param url            包的url地址
     * @return 处理后的包名，以供下次调用使用
     */
    private static String findAndAddClassesInPackageByJar(String packageName,
                                                          List<String> classNameList,
                                                          boolean recursive,
                                                          String packageDirName,
                                                          URL url) {
        try {
            JarFile jar = ((JarURLConnection) url.openConnection()).getJarFile();
            Enumeration<JarEntry> entries = jar.entries();
            while (entries.hasMoreElements()) {
                //获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
                JarEntry entry = entries.nextElement();
                String name = entry.getName();
                // 如果是以/开头的
                if (name.charAt(0) == '/') {
                    // 获取后面的字符串
                    name = name.substring(1);
                }
                if (name.startsWith(packageDirName)) {
                    int idx = name.lastIndexOf('/');
                    if (idx != -1) {
                        // 如果以/结尾是一个包，获取包名，将/替换成.
                        packageName = name.substring(0, idx).replace('/', '.');
                    }
                    if (idx != -1 || recursive) {
                        //如果是一个.class文件 而且不是目录
                        if (name.endsWith(CLASS_FILE_SUFFIX) && !entry.isDirectory()) {
                            // 去掉后面的.class获取真正的类名
                            String className = name.substring(packageName.length() + 1, name.length() - 6);
                            classNameList.add(packageName + '.' + className);
                        }
                    }
                }
            }
        } catch (IOException e) {
            log.error("findAddAddClassesInPackageByJar|Has a exception,", e);
        }
        return packageName;
    }
}
